﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Xml.Serialization;
using System.IO;
using System.Threading;

namespace MazeNet.Communication
{
    /// <summary>
    /// Client zur Verbindung mit einem MazeNet Server
    /// </summary>
    public class MazeNetClient
    {
        #region Properties

        /// <summary>
        /// Adresse des MazeNet Servers
        /// </summary>
        public readonly IPEndPoint ServerEndPoint;

        public bool Connected { get { return (_mSocket != null && _mSocket.Connected); } }
        public int Id { get { return _mId; } }
        public EventHandler<MazeNetEventArgs> OnMessageReceived { get; set; }
        public EventHandler<MazeNetEventArgs> OnMessageSent { get; set; }

        #endregion

        #region Member

        private Socket _mSocket;
        private int _mId;
        private XmlSerializer _mSerializer;

        #endregion

        /// <summary>
        /// Erstellt einen neuen MazeNet Client.
        /// </summary>
        /// <param name="_uEndPoint">Adresse des Servers</param>
        public MazeNetClient(IPEndPoint _uEndPoint)
        {
            ServerEndPoint = _uEndPoint;
            _mSerializer = new XmlSerializer(typeof(MazeCom));
        }

        /// <summary>
        /// Baut Verbindung zum Server auf
        /// </summary>
        /// <returns>true, falls Verbindungsversuch erfolgreich</returns>
        public bool Connect()
        {
            bool _lReturn = false;
            if (_mSocket != null && _mSocket.Connected)
            {
                try
                {
                    Disconnect();
                    _mSocket.Connect(ServerEndPoint);
                    this.ReceiveMessage();
                    _lReturn = true;
                }
                catch
                {
                    _mSocket = null;
                }
            }
            else
            {
                _mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                try
                {
                    _mSocket.Connect(ServerEndPoint);
                    _lReturn = true;
                }
                catch
                {
                    _mSocket = null;
                }
            }

            return _lReturn;
        }

        /// <summary>
        /// Beendet Verbindung zum Server
        /// </summary>
        public void Disconnect()
        {
            if ((_mSocket != null) && (_mSocket.Connected))
            {
                try
                {
                    _mSocket.Disconnect(true);
                }
                catch { }
            }
        }

        /// <summary>
        /// Fuehrt Login auf Server durch und beginnt danach mit Empfang von Nachrichten
        /// </summary>
        /// <param name="_uName">Name, mit dem sich angemeldet wird.</param>
        public void LogIn(string _uName)
        {
            MazeCom _lMessage;
            LoginMessageType _lLogInMessage;

            //Nachricht erstellen
            _lMessage = new MazeCom();
            _lMessage.mcType = MazeComType.LOGIN;
            _lLogInMessage = new LoginMessageType();
            _lLogInMessage.name = _uName;
            _lMessage.Item = _lLogInMessage;

            //Nachricht SENDEN
            SendMazeCom(_lMessage);

            //Ab nun wird empfangen
            new Thread((object o) => ReceiveMessage()).Start();
        }

        /// <summary>
        /// Empfaengt Nachrichten von Server und leitet diese an 
        /// HandleIncomingMessage() weiter.
        /// </summary>
        private void ReceiveMessage()
        {
            NetworkStream _lInStream;
            MazeCom _lMessageIn;
            byte[] _lBuffer;
            int _lRead;
            int _lNeed;

            try
            {
                _lInStream = new NetworkStream(_mSocket);
                while (this.Connected)
                {
                    _lRead = 0;
                    _lBuffer = new byte[4];
                    _lInStream.Read(_lBuffer, 0, 4);
                    _lNeed = BitConverter.ToInt32(_lBuffer, 0);
                    _lBuffer = new byte[_lNeed];
                    while (_lRead < _lNeed)
                    {
                        _lRead += _lInStream.Read(_lBuffer, _lRead, _lNeed-_lRead);
                    }
                    _lMessageIn = (MazeCom)_mSerializer.Deserialize(new StringReader(Encoding.UTF8.GetString(_lBuffer)));

                    //ggf neue ID setzen
                    if (_lMessageIn.mcType == MazeComType.LOGINREPLY)
                    {
                        this._mId = ((LoginReplyMessageType)_lMessageIn.Item).newID;
                    }
                    //ggf Schließen
                    if ((_lMessageIn.mcType == MazeComType.DISCONNECT) || (_lMessageIn.mcType == MazeComType.WIN))
                    {
                        this.Disconnect();
                    }

                    //Event ueber neue Nachricht absetzen
                    if (OnMessageReceived != null)
                    {
                        OnMessageReceived(this, new MazeNetEventArgs(_lMessageIn));
                    }
                }
            }
            catch (Exception e1)
            {
                Console.WriteLine(e1.Message);
                Disconnect();
            }
        }

        /// <summary>
        /// Sendet ein MazeCom Objekt an den Server
        /// </summary>
        /// <param name="_uMazeCom">Zu sendendes MazeCom Objekt</param>
        public void SendMazeCom(MazeCom _uMazeCom)
        {
            UTF8StringWriter _lWriter;
            //string _lByteOrderMark = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());          
            String _lString;

            //Nachricht Serialisieren
            _lWriter = new UTF8StringWriter();
            _mSerializer.Serialize(_lWriter, _uMazeCom);
            _lString = _lWriter.ToString();
            //_lString = _lString.Substring(_lString.IndexOf('>') + 1);

            //Nachricht SENDEN
            _mSocket.Send(BitConverter.GetBytes(Encoding.UTF8.GetByteCount(_lString)));
            _mSocket.Send(Encoding.UTF8.GetBytes(_lString));

            //Event
            if (OnMessageSent != null)
            {
                OnMessageSent(this, new MazeNetEventArgs(_uMazeCom));
            }
        }

    }

}
